{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Advanced Pandas functionality \n",
    "## - DataFrame.apply()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## Introduction\n",
    "* We now try to use Pandas DataFrames to hold objects instead of numbers\n",
    "* Process all Columns or Rows using the .apply .map methods"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import matplotlib.pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "### Preparing test data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "First we generate some objects, namely 100 numpy arrays containing 500 random values each:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "curves = [np.random.randn(500) for i in range(100)]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "Then we generate some random ids for the curves (This could be Tube-IDs):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([82590, 55844, 31985, 61340, 74576, 76616, 27787, 76249, 83860,\n",
       "       19898, 27529, 41697, 52773, 53054, 26543, 21920, 45355, 25344,\n",
       "       71838, 91207, 53333, 93331, 10537, 96239, 93528, 43127, 18427,\n",
       "       75788, 70301, 10901, 54968, 95862, 82471, 99576, 74093, 59369,\n",
       "       17133, 96882, 57381, 52192, 97838, 62893, 74781, 75079, 75000,\n",
       "       56897, 25269, 91873, 76151, 19890, 63756, 33356, 79946, 23777,\n",
       "       95785, 26768, 77871, 48135, 59609, 19166, 39150, 31924, 59096,\n",
       "       91456, 10628, 17015, 66773, 34167, 80354, 71051, 29980, 53992,\n",
       "       36534, 53577, 66095, 26716, 59311, 31984, 53482, 68444, 76486,\n",
       "       85529, 85286, 91226, 76524, 55389, 13746, 94707, 28957, 67612,\n",
       "       92276, 16596, 26332, 89707, 24263, 40422, 64275, 35380, 21453,\n",
       "       23506])"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ids = np.random.choice(range(10000, 99999), 100, replace=False)\n",
    "ids"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    ".. and put everything into a Series:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 30,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "s1 = pd.Series(data=curves, \n",
    "               index=ids, \n",
    "               name='first_sensor')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "Finally we make a DataFrame from it:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>first_sensor</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>82590</th>\n",
       "      <td>[0.7001446564274595, -0.6202553402125156, 0.83...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>55844</th>\n",
       "      <td>[-0.8794582998349463, 0.8375811217847537, -0.3...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>31985</th>\n",
       "      <td>[-2.370463320766105, 0.6998033482535078, 1.077...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>61340</th>\n",
       "      <td>[-2.201685429659071, -0.7434162727229623, -2.2...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>74576</th>\n",
       "      <td>[1.4860213731146745, -0.6830079893991837, 0.62...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                            first_sensor\n",
       "82590  [0.7001446564274595, -0.6202553402125156, 0.83...\n",
       "55844  [-0.8794582998349463, 0.8375811217847537, -0.3...\n",
       "31985  [-2.370463320766105, 0.6998033482535078, 1.077...\n",
       "61340  [-2.201685429659071, -0.7434162727229623, -2.2...\n",
       "74576  [1.4860213731146745, -0.6830079893991837, 0.62..."
      ]
     },
     "execution_count": 31,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df1 = s1.to_frame()\n",
    "df1.head(5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "For demonstration purposes we now add Measurements from a second sensor:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 32,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [],
   "source": [
    "curves_from_sensor_2 = [np.random.randn(500) for i in range(100)]\n",
    "s2 = pd.Series(data=curves_from_sensor_2, \n",
    "               index=pd.Index(ids, 'int64', name='ID'), \n",
    "               name='second_sensor')\n",
    "df2 = s2.to_frame()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>first_sensor</th>\n",
       "      <th>second_sensor</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>82590</th>\n",
       "      <td>[0.7001446564274595, -0.6202553402125156, 0.83...</td>\n",
       "      <td>[-0.678286071981691, 1.4435591485656292, -0.69...</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>55844</th>\n",
       "      <td>[-0.8794582998349463, 0.8375811217847537, -0.3...</td>\n",
       "      <td>[1.3725975495992109, 0.40835253873105787, 0.24...</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                                            first_sensor  \\\n",
       "82590  [0.7001446564274595, -0.6202553402125156, 0.83...   \n",
       "55844  [-0.8794582998349463, 0.8375811217847537, -0.3...   \n",
       "\n",
       "                                           second_sensor  \n",
       "82590  [-0.678286071981691, 1.4435591485656292, -0.69...  \n",
       "55844  [1.3725975495992109, 0.40835253873105787, 0.24...  "
      ]
     },
     "execution_count": 33,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "df = df1.join(df2)\n",
    "df.head(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "# Applying functions"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 1. `DataFrame.apply()`\n",
    "We now want to calculate some summarizing statistics on the curves. Therefore we use `.apply()` on the dataframe. The function called by `.apply` gets the columns (`axis=0`) or the rows (`axis=1`) of the dataframe one by one as input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "scrolled": true,
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "82590   -0.055035\n",
       "55844    0.020998\n",
       "Name: mean_of_first_sensor, dtype: float64"
      ]
     },
     "execution_count": 34,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def _calculate_mean_of_sensor(row, column='first_sensor'):\n",
    "    single_curve = row[column]    \n",
    "    return np.mean(single_curve)\n",
    "\n",
    "# Axis=1 applies Row-Wise!!\n",
    "mean_of_first_sensor = df.apply(_calculate_mean_of_sensor, axis=1).rename('mean_of_first_sensor')\n",
    "mean_of_first_sensor.head(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "A function can use multiple columns for calculation. Lets say we want to calculate the difference of the means from sensor 1 and sensor 2:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "82590    0.088417\n",
       "55844    0.008528\n",
       "Name: mean_difference, dtype: float64"
      ]
     },
     "execution_count": 35,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def _get_mean_difference(row, first_sensor='first_sensor', second_sensor='second_sensor'):\n",
    "    sensor_1_curve = row[first_sensor]\n",
    "    sensor_2_curve = row[second_sensor]\n",
    "    \n",
    "    return np.abs(np.mean(sensor_1_curve) - np.mean(sensor_2_curve))\n",
    "\n",
    "mean_difference = df.apply(_get_mean_difference, axis=1).rename('mean_difference')\n",
    "mean_difference.head(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "subslide"
    }
   },
   "source": [
    "Functions can also have multiple outputs. In this case we return a pd.Series:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>Mean_Curve_1</th>\n",
       "      <th>Mean_Curve_2</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>82590</th>\n",
       "      <td>-0.055035</td>\n",
       "      <td>0.033382</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>55844</th>\n",
       "      <td>0.020998</td>\n",
       "      <td>0.012470</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       Mean_Curve_1  Mean_Curve_2\n",
       "82590     -0.055035      0.033382\n",
       "55844      0.020998      0.012470"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def _get_mean_difference(row, first_sensor='first_sensor', second_sensor='second_sensor'):\n",
    "    sensor_1_curve = row[first_sensor]\n",
    "    sensor_2_curve = row[second_sensor]\n",
    "    mean_curve_1 = np.mean(sensor_1_curve)\n",
    "    mean_curve_2 = np.mean(sensor_2_curve)\n",
    " \n",
    "    return pd.Series({'Mean_Curve_1': mean_curve_1, 'Mean_Curve_2': mean_curve_2})\n",
    "\n",
    "means = df.apply(_get_mean_difference, axis=1)\n",
    "means.head(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 2. `DataFrame.map()`"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "source": [
    "If we want to apply the SAME function to ALL fields of the table, and not row or columnwise, we can use `.map()`. Here we calculate the length of each curve:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>length_first_sensor</th>\n",
       "      <th>length_second_sensor</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>82590</th>\n",
       "      <td>500</td>\n",
       "      <td>500</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>55844</th>\n",
       "      <td>500</td>\n",
       "      <td>500</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "       length_first_sensor  length_second_sensor\n",
       "82590                  500                   500\n",
       "55844                  500                   500"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "lengths = df.map(len).add_prefix('length_')\n",
    "lengths.head(2)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "slideshow": {
     "slide_type": "slide"
    }
   },
   "source": [
    "## 3. Series.apply()\n",
    "`Series.apply()` applies the function simply to each field of the Series. This is very similar to `DataFrame.map()`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "slideshow": {
     "slide_type": "-"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "82590    500\n",
       "55844    500\n",
       "Name: first_sensor, dtype: int64"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "s1.apply(len).head(2)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "RL_course_py12",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.9"
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
